/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

	srec.cpp: Sound recording class

	Primary Author: ****** (based on ***** original srec)
	Review Status: reviewed

***************************************************************************/
#include "soc.h"

ASSERTNAME

RTCLASS(SREC)

/***************************************************************************
	Create a new SREC
***************************************************************************/
PSREC SREC::PsrecNew(long csampSec, long cchan, long cbSample, ulong dtsMax)
{
	PSREC psrec;

	psrec = NewObj(SREC);
	if (pvNil == psrec)
		return pvNil;
	if (!psrec->_FInit(csampSec, cchan, cbSample, dtsMax))
		{
		ReleasePpo(&psrec);
		return pvNil;
		}
	AssertPo(psrec, 0);
	return psrec;
}


/***************************************************************************
	Init this SREC
***************************************************************************/
bool SREC::_FInit(long csampSec, long cchan, long cbSample, ulong dtsMax)
{
	AssertBaseThis(0);
	AssertIn(cchan, 0, ksuMax);

	long cwid;

	_csampSec = csampSec;
	_cchan = cchan;
	_cbSample = cbSample;
	_dtsMax = dtsMax;
	_hwavein = pvNil;
	_priff = pvNil;
	_fBufferAdded = fFalse;
	_fRecording = fFalse;
	_fHaveSound = fFalse;

	vpsndm->Suspend(fTrue); // turn off sndm so we can get wavein device

	// See if sound recording is possible at all
	cwid = waveInGetNumDevs();
	if (0 == cwid)
		{
		PushErc(ercSocNoWaveIn);
		return fFalse;
		}

	// allocate a 10 second buffer
	_wavehdr.dwBufferLength = (cchan * csampSec * cbSample * dtsMax)/1000;
	if(!FAllocPv((void **)&_priff, sizeof(RIFF) + _wavehdr.dwBufferLength, fmemClear, mprNormal))
		return fFalse;
	
	_wavehdr.lpData = (LPBYTE)PvAddBv(_priff, sizeof(RIFF));

	// init RIFF structure
	_priff->Set(_cchan, _csampSec, _cbSample, 0);

	if (fFalse == _FOpenRecord())
		{
		return fFalse;
		}

	// get audioman 
	_pmixer = GetAudioManMixer();
	if (pvNil == _pmixer)
		return fFalse;

	// get a channel
	_pmixer->AllocChannel(&_pchannel);
	if (pvNil == _pchannel)
		return fFalse;

	return fTrue;
}


/***************************************************************************
	Clean up and delete this SREC
***************************************************************************/
SREC::~SREC(void)
{
	AssertBaseThis(0);
	
	// make sure nothing is playing or recording
	if (_fRecording || _fPlaying)
		FStop();

	if (_hwavein)
		_FCloseRecord();

	ReleasePpo(&_pchannel);
	ReleasePpo(&_pmixer);
	FreePpv((void **)&_priff);
	vpsndm->Suspend(fFalse); 	// restore sound mgr
}


/***************************************************************************
	Open Device for recording
***************************************************************************/
bool SREC::_FOpenRecord(void)
{
	AssertBaseThis(0);

	_fRecording = fFalse;

	if (pvNil == _hwavein)
		{
		// open a wavein device
		if (waveInOpen(&_hwavein, WAVE_MAPPER, (WAVEFORMAT *)_priff->PwfxGet(), (DWORD)_WaveInProc, (DWORD)this, CALLBACK_FUNCTION))
			{
			// it doesn't support this format
			return fFalse;
			}

		// prepare header on block of data
		_wavehdr.dwUser = (DWORD) this;
		if (waveInPrepareHeader(_hwavein, &_wavehdr, sizeof(WAVEHDR)))
			{
			waveInClose(_hwavein);
			_hwavein = pvNil;
			return fFalse;
			}
		}

	// add buffer to device
	if (!_fBufferAdded)
		if (waveInAddBuffer(_hwavein, &_wavehdr, sizeof(WAVEHDR)))
			{
			_FCloseRecord();
			_fRecording = fFalse;
			_hwavein = pvNil;
			return fFalse;
			}
		else
			_fBufferAdded = fTrue;

	return fTrue;
}

/***************************************************************************
	Close Device for recording
***************************************************************************/
bool SREC::_FCloseRecord(void)
{
	AssertThis(0);

	if (_hwavein)
		{
		// stop if necessary
		waveInReset(_hwavein);

		// unprepare header
		waveInUnprepareHeader(_hwavein, &_wavehdr, sizeof(WAVEHDR));
		_fRecording = fFalse;
				
		// close
		waveInClose(_hwavein);
		_hwavein = pvNil;
		}

	return fTrue;
}


/***************************************************************************
	Figure out if we're recording or not
***************************************************************************/
void SREC::_UpdateStatus(void)
{
	AssertThis(0);

	// ------------------------------------ 
	// Check playing mode
	// ------------------------------------ 
	if ((_fPlaying) && !_pchannel->IsPlaying())
		{
		// then we just stopped
		Sleep(250L);	// sleep a little bit to cover AudioMan bug 
		vpsndm->Suspend(fTrue); 	// suspend sound mgr
		}
	_fPlaying = _pchannel->IsPlaying();

	// ------------------------------------ 
	// 	Check Recording mode
	// If we are recording, AND our HaveSound flag
	// is set, then we must have just finished, so 
	// process the data, and turn off the recording flag
	// ------------------------------------ 
	if ((_fRecording) && (_fHaveSound))
		{
		LPSOUND psnd = pvNil;		// original psnd
		LPSOUND psndBias = pvNil;	// psnd Bias correction filter
		LPSOUND psndTrim = pvNil;	// psnd Trim filter

		_fRecording = fFalse;
		if (_wavehdr.dwBytesRecorded == 0)
			{
			_fHaveSound = fFalse;
			return;
			}

		// using the Audioman APIs, apply the gain and Trim filter, and save it back out
		// to a different temp file.
		_wavehdr.dwBytesRecorded -= 8*(_cchan * _cbSample);	// chop off last 8 samples worth, since some audio cards put garbage on end of data
		_priff->Set(_cchan, _csampSec, _cbSample, _wavehdr.dwBytesRecorded);

		// now use AudioMan API to load the temp file, apply a trim filter and place
		// trimmed sound out to our temp file
		if (FAILED(AllocSoundFromMemory(&psnd, (LPBYTE)_priff, _priff->Cb())))
			{
			PushErc(ercOomNew);
			_fHaveSound = fFalse;	
			return;
			}
		_fHaveSound = fTrue;

		if (FAILED(AllocBiasFilter(&psndBias, psnd)))
			{
			// then just return the sound raw
			_psnd = psnd;
			return;
			}

		// release the original sound, since it's now owned by the psndGain
		ReleasePpo(&psnd);

		if (FAILED(AllocTrimFilter(&_psnd, psndBias)))
			{
			// then just return the sound with the bias filter on it...
			_psnd = psndBias;
			return;
			}
		// release the psndBias, since it's now owned by the psndTrim
		ReleasePpo(&psndBias);
		}
}

/***************************************************************************
	Figure out if we're recording or not
***************************************************************************/
void SREC::_WaveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	// the psrec pointer is a pointer to the class which generated the event and owns the device
	SREC *psrec = (SREC *)dwInstance;

	switch(uMsg)
		{
		case WIM_DATA:
			{
			// any time we get a block of data, we are done, we set our flag
			// to true, allowing _UpdateStatus to notice that we are _fRecording and _fHaveSound
			// at which point it will process the data...
			psrec->_fHaveSound = fTrue;
			psrec->_fBufferAdded = fFalse;
			}
		}
}

/***************************************************************************
	Start recording
***************************************************************************/
bool SREC::FStart(void)
{
	AssertThis(0);
	Assert(!_fRecording, "stop previous recording first");

	// make sure we are open
	if (_fPlaying)
		FStop();

	if (!_FOpenRecord())
		return fFalse;

	_fHaveSound = fFalse;
	_fRecording = fFalse;
	_wavehdr.dwBytesRecorded = 0;
	
	// now record data
	if (waveInStart(_hwavein))
		return fFalse;

	_fRecording = fTrue;

	return fTrue;
}

/***************************************************************************
	Stop recording or playing
***************************************************************************/
bool SREC::FStop(void)
{
	AssertThis(0);
	Assert(_fRecording || _fPlaying, "Nothing to stop");

	// if we are recording
	if (_fRecording)
		{
		// then stop the recording device
		waveInStop(_hwavein);
		}
	else if (_fPlaying)	// if we are playing
		{
		// then stop the playing device
		_pchannel->Stop();
		}

	// update status accordingly
	_UpdateStatus();

	return fTrue;
}


/***************************************************************************
	Start playing the current sound
***************************************************************************/
bool SREC::FPlay(void)
{
	AssertThis(0);
	Assert(_fHaveSound, "No sound to play");
	
	// open the _fniTrim file with MCI
	_FCloseRecord();

	if (_psnd && _pchannel)
		{
		vpsndm->StopAll(); 				//stop any outstanding bogus sounds from button pushs
		vpsndm->Suspend(fFalse); 		// restore sound mgr

		_pchannel->Stop();				// stop our channel (should be nop)
		_pchannel->SetSoundSrc(_psnd);	// give it our sound
		_pchannel->SetPosition(0);		// seek to the beginning

		if (FAILED(_pchannel->Play()))	// play the sound
			{
			_UpdateStatus();		// this will check play status, and clean up accordingly
			}
		else
			_fPlaying = fTrue;	
		}

	return _fPlaying;
}


/***************************************************************************
	Are we recording?
***************************************************************************/
bool SREC::FRecording(void)
{
	AssertThis(0);

	_UpdateStatus();
	return _fRecording;
}


/***************************************************************************
	Are we playing the current sound?
***************************************************************************/
bool SREC::FPlaying(void)
{
	AssertThis(0);

	_UpdateStatus();
	return _fPlaying;
}


/***************************************************************************
	Save the current sound to the given FNI
***************************************************************************/
bool SREC::FSave(PFNI pfni)
{
	AssertThis(0);
	Assert(_fHaveSound, "Nothing to save!");

	STN stn;

	if (_psnd)
		{
		pfni->GetStnPath(&stn);

		// now save _psnd to the FNI passed in 
		if (FAILED(SoundToFileAsWave(_psnd, stn.Psz())))
			{
			PushErc(ercSocWaveSaveFailure);	
			return fFalse;
			}
		return fTrue;
		}
	return fFalse;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of the SREC.
***************************************************************************/
void SREC::AssertValid(ulong grf)
{
	SREC_PAR::AssertValid(fobjAllocated);
	Assert(pvNil != _pmixer, "No mixer?");
	Assert(pvNil != _pchannel, "No Channel?");
}


/***************************************************************************
	Mark memory used by the SREC
***************************************************************************/
void SREC::MarkMem(void)
{
	AssertThis(0);
	MarkPv(_priff);
	SREC_PAR::MarkMem();
}
#endif //DEBUG

